package fr.bugtracker.bean.security;

import org.springframework.dao.DataAccessException;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;
import org.springframework.security.authentication.dao.SaltSource;
import org.springframework.security.authentication.encoding.PasswordEncoder;
import org.springframework.security.authentication.encoding.ShaPasswordEncoder;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;

import fr.bugtracker.commun.exception.ExceptionFonctionnelle;
import fr.bugtracker.commun.outils.UtilsMetier;
import fr.bugtracker.service.outils.SrvMetier;

/**
 * @author Laurent
 * @version 0.1
 */
public class CustomAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {

	/**
	 *
	 */
	private PasswordEncoder passwordEncoder = new ShaPasswordEncoder();
	/**
	 *
	 */
	private SaltSource saltSource;
	/**
	 *
	 */
	private UserDetailsService userDetailsService;
	/**
	 *
	 */
    private boolean includeDetailsObject = true;

	/**
	 * @see org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider#additionalAuthenticationChecks(org.springframework.security.core.userdetails.UserDetails, org.springframework.security.authentication.UsernamePasswordAuthenticationToken)
	 */
	@Override
	protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
		Object salt = null;

		if (this.saltSource != null) {
			salt = this.saltSource.getSalt(userDetails);
		}

		if (authentication.getCredentials() == null) {
			throw new BadCredentialsException("Veuillez renseigner le nom d'utilisateur et le mot de passe avant de vous authentifier");
		}

		String presentedPassword = authentication.getCredentials().toString();
		if (!passwordEncoder.isPasswordValid(userDetails.getPassword(), presentedPassword, salt)) {
			throw new BadCredentialsException("Nom d'utilisateur ou mot de passe incorrect");
		}
	}

	/**
	 * @see org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider#retrieveUser(java.lang.String, org.springframework.security.authentication.UsernamePasswordAuthenticationToken)
	 */
	@Override
	protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
		UserDetails user;
		if ((username == null) || username.isEmpty()) {
			throw new AuthenticationServiceException("Veuillez correctement renseigner le nom d'utilisateur");
		}
		try {
			try {
				user = UtilsMetier.getService(SrvMetier.class).getUtilisateurByUsername(username);
			} catch (ExceptionFonctionnelle e) {
				throw new AuthenticationServiceException(e.getMessage());
			}
		} catch (DataAccessException repositoryProblem) {
			throw new AuthenticationServiceException("Nom d'utilisateur ou mot de passe incorrect");
		}
		return user;
	}

	/**
	 * <p><b>Accès en lecture à l'attribut passwordEncoder.</b></p>
	 * @return Retourne l'attribut passwordEncoder sous forme de <code>PasswordEncoder</code>.
	 */
	public PasswordEncoder getPasswordEncoder() {
		return this.passwordEncoder;
	}

	/**
	 * <p><b>Accès en écriture à l'attribut passwordEncoder.</b></p>
	 * @param passwordEncoder L'attribut passwordEncoder à modifier.
	 */
	public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
		this.passwordEncoder = passwordEncoder;
	}

	/**
	 * <p><b>Accès en lecture à l'attribut saltSource.</b></p>
	 * @return Retourne l'attribut saltSource sous forme de <code>SaltSource</code>.
	 */
	public SaltSource getSaltSource() {
		return this.saltSource;
	}

	/**
	 * <p><b>Accès en écriture à l'attribut saltSource.</b></p>
	 * @param saltSource L'attribut saltSource à modifier.
	 */
	public void setSaltSource(SaltSource saltSource) {
		this.saltSource = saltSource;
	}

	/**
	 * <p><b>Accès en lecture à l'attribut userDetailsService.</b></p>
	 * @return Retourne l'attribut userDetailsService sous forme de <code>UserDetailsService</code>.
	 */
	public UserDetailsService getUserDetailsService() {
		return this.userDetailsService;
	}

	/**
	 * <p><b>Accès en écriture à l'attribut userDetailsService.</b></p>
	 * @param userDetailsService L'attribut userDetailsService à modifier.
	 */
	public void setUserDetailsService(UserDetailsService userDetailsService) {
		this.userDetailsService = userDetailsService;
	}

	/**
	 * <p><b>Accès en lecture à l'attribut includeDetailsObject.</b></p>
	 * @return Retourne l'attribut includeDetailsObject sous forme de <code>boolean</code>.
	 */
	public boolean isIncludeDetailsObject() {
		return this.includeDetailsObject;
	}

	/**
	 * <p><b>Accès en écriture à l'attribut includeDetailsObject.</b></p>
	 * @param includeDetailsObject L'attribut includeDetailsObject à modifier.
	 */
	public void setIncludeDetailsObject(boolean includeDetailsObject) {
		this.includeDetailsObject = includeDetailsObject;
	}

}
